
class Value:
    """ stores a single scalar value and its gradient """
    #存储标量值和它的梯度
    def __init__(self, data, _children=(), _op=''):
        self.data = data
        self.grad = 0
        # internal variables used for autograd graph construction
        #用于自动求导图内部构建的变量
        self._backward = lambda: None #lambda表达式，匿名函数，这个值对应的进行反向传播的操作
        self._prev = set(_children) #set（）用于创建一个无序但是元素为一的集合，这个是用于记住每个节点有那些得来的，记住上一级节点
        #生成此节点的操作，用于调试
        self._op = _op # the op that produced this node, for graphviz / debugging / etc

#这里__add__是类自带的方法，这里在重写
    #以__add__为例子来讲解一下一些变量的含义
    #_children是一个元组记录的是对应结果由哪两个数据得来
    #_op是记录某个节点的操作，方便之后的可视化和调试
    def __add__(self, other):
        #isinstance(object, classinfo)，检查object是否为calssinfo类型
        other = other if isinstance(other, Value) else Value(other) #py的三元表达式，也叫条件表达式，这里先是检查了other类型啊
        out = Value(self.data + other.data, (self, other), '+')
        #这下面是在算梯度，举个简单的例子c=a+b，梯度就是某个值对最终损失函数影响的一个量化指标，然后已经知道损失函数关于c的梯度，然后通过链式法则
        #就可以知道损失函数分别对a和b的梯度，这里就是利用了这点在算梯度。
        def _backward():
            self.grad += out.grad
            other.grad += out.grad
        #这里out._backward是一个函数对象，存储了一步反向传播的函数
        out._backward = _backward
#看到这个函数对象，可能就有小聪明要问了，不是我后续调用的时候没传参，怎么知道other的，这就不得不说py的闭包机制了。
#py的函数中再定义函数的时候会触发闭包机制，自动捕获外部变量，然后我这里out._backward不仅记住了一个函数，还记住了函数中当时调用的变量的值
        return out
#这个重载的是乘法
    def __mul__(self, other):
        other = other if isinstance(other, Value) else Value(other)
        out = Value(self.data * other.data, (self, other), '*')
        #这里梯度的计算也好理解，c=a*b，然后用链式法则很容易推导出来
        def _backward():
            self.grad += other.data * out.grad
            other.grad += self.data * out.grad
        out._backward = _backward

        return out
#这里重载的是幂运算，2**2，2的2次方，**就是幂运算
    def __pow__(self, other):
        #isinstance(object, (type1, type2, ...))这是isinstance多类型检查的语法，可以检查object是否在后续元组的类型中
        #assert 条件表达式, "错误信息" ，assert在程序中设置断言，条件不满足程序抛出错误且抛出设置的错误信息
        assert isinstance(other, (int, float)), "only supporting int/float powers for now"
        out = Value(self.data**other, (self,), f'**{other}') #(self,)是创建只含一个元素的元组的做法，下面来分析一下为什么这里只有一个元素参与运算
        #

        def _backward():
            self.grad += (other * self.data**(other-1)) * out.grad
        out._backward = _backward

        return out
    #自定义的激活函数
    def relu(self):
        out = Value(0 if self.data < 0 else self.data, (self,), 'ReLU')
        #对经过激活函数的东西求梯度
        def _backward():
            self.grad += (out.data > 0) * out.grad  #out.data > 0会返回0或1，由链式法则可得
        out._backward = _backward

        return out
#下面这个函数是py的特色了，自定义的函数里面还可以有自定义的函数
    def backward(self):

        # topological order all of the children in the graph 对图中所有子节点进行拓扑排序
        #拓扑排序是针对有向无环图而言的，就是使得图中任何有向边，顶点u始终在顶点v前，这里把前向传播抽象成了有向无环图
        topo = []
        visited = set()
        def build_topo(v):
            if v not in visited:
                visited.add(v)
                for child in v._prev:
                    build_topo(child)
                topo.append(v)
        build_topo(self)

        # go one variable at a time and apply the chain rule to get its gradient 一次处理一个变量并且使用链式法则获得梯度
        self.grad = 1 #自动微分的最终节点一般是损失函数本身，其本身对于自己求导梯度为1
        for v in reversed(topo):#reversed(topo)这个函数作用是将topo的顺序颠倒，就是成为一个反向的迭代器
            v._backward()
#下面这些都没有具体实现
    def __neg__(self): # -self
        return self * -1

    def __radd__(self, other): # other + self
        return self + other

    def __sub__(self, other): # self - other
        return self + (-other)

    def __rsub__(self, other): # other - self
        return other + (-self)

    def __rmul__(self, other): # other * self
        return self * other

    def __truediv__(self, other): # self / other
        return self * other**-1

    def __rtruediv__(self, other): # other / self
        return other * self**-1

    def __repr__(self):
        return f"Value(data={self.data}, grad={self.grad})"
